/*
 *  a wrap for non-block socket class for Windows, LINUX and MacOS X platform
 */

#ifndef __SOCKET_H__
#define __SOCKET_H__

#include <string>
#include "util.h"
#include "ostype.h"

enum
{
	SOCKET_STATE_IDLE,
	SOCKET_STATE_LISTENING,
	SOCKET_STATE_CONNECTING,
	SOCKET_STATE_CONNECTED,
	SOCKET_STATE_CLOSING
};

class CBaseSocket : public CRefObject
{
public:
	using string = std::string;
private:
	string 		m_remote_ip;
	uint16_t 	m_remote_port;
	string 		m_local_ip;
	uint16_t 	m_local_port;

	callback_t 	m_callback;
	void 	   *m_callback_data;

	uint8_t 	m_state;
	SOCKET 		m_socket;
public:
	CBaseSocket();

	virtual ~CBaseSocket();

	SOCKET GetSocket() const 		{ return m_socket; }
	void SetSocket(SOCKET fd) 		{ m_socket = fd; }
	void SetState(uint8_t state) 	{ m_state = state; }

	void SetCallback(callback_t callback) 	{ m_callback = callback; }
	void SetCallbackData(void *data) 		{ m_callback_data = data; }
	void SetRemoteIP(char *ip) 				{ m_remote_ip = ip; }
	void SetRemotePort(uint16_t port) 		{ m_remote_port = port; }
	void SetSendBufSize(uint32_t send_size);
	void SetRecvBufSize(uint32_t recv_size);

	const char *GetRemoteIP() const	{ return m_remote_ip.c_str(); }
	uint16_t GetRemotePort()  const { return m_remote_port; }
	const char *GetLocalIP()  const { return m_local_ip.c_str(); }
	uint16_t GetLocalPort()   const { return m_local_port; }

public:
	/// @brief listen ip:port and add a events monitor
	/// @param server_ip 
	/// @param port 
	/// @param callback this is called when the events of socket is triggered
	/// @param callback_data 
	/// @return 
	int Listen(
		const char *server_ip,
		uint16_t port,
		callback_t callback,
		void *callback_data);

	/// @brief connection ip:port is used on client side. noblock and nodelay.
	/// @param server_ip server ip
	/// @param port server port
	/// @param callback 
	/// @param callback_data 
	/// @return 
	net_handle_t Connect(
		const char *server_ip,
		uint16_t port,
		callback_t callback,
		void *callback_data);

	/// @brief Send data to socket, and when a failure occurs, give it to the event monitor for processing.
	/// If error is BLOCK, give it to the event monitor for processing.
	/// If error is other, It will jump out this function and return -1;
	/// @param buf Data of be sent. 
	/// @param len Length of buf.
	/// @return Number of bytes written.
	int Send(void *buf, int len);

	int Recv(void *buf, int len);

	int Close();

public:
	void OnRead();
	void OnWrite();
	void OnClose();

private:
	int _GetErrorCode();
	bool _IsBlock(int error_code);

	void _SetNonblock(SOCKET fd);
	void _SetReuseAddr(SOCKET fd);
	void _SetNoDelay(SOCKET fd);
	void _SetAddr(const char *ip, const uint16_t port, sockaddr_in *pAddr);

	/// @brief Accept new connection from client.
	void _AcceptNewSocket();
};

/// @brief find socket from global socket map.
/// If existed, the socket erference count is add one. 
/// @param fd 
/// @return return nullptr if doesn't exist.
CBaseSocket *FindBaseSocket(net_handle_t fd);

#endif
